#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"

//子进程工作函数 work(fd[0])
// void work(int rfd)
// {
//     while (true)
//     {
//         int command = 0;
//         int n = read(rfd, &command, sizeof(command));
//          //表示读到的大小
//         if (n == sizeof(int))
//         {
//             std::cout << "pid is : " << getpid() << " handler task" << std::endl;
//             Excut eTask(command);
//         }
            //关闭写端，read()返回值为0表示读到文件末尾了
//         else if (n == 0)
//         {
//             std::cout << "sub process : " << getpid() << " quit" << std::endl;
//             break;
//         }
//     }
// }

//封装一个信道
// master
class Channel
{
public:
    Channel(int wfd, pid_t id, const std::string &name)
        : _wfd(wfd), _subprocessid(id), _name(name)
    {
    }
    int GetWfd() { return _wfd; }
    pid_t GetProcessId() { return _subprocessid; }
    std::string GetName() { return _name; }
    //关闭写描述符
    void CloseChannel()
    {
        close(_wfd);
    }
    //回收子进程
    void Wait()
    {
        pid_t rid = waitpid(_subprocessid, nullptr, 0);
        if (rid > 0)
        {
            std::cout << "wait " << rid << " success" << std::endl;
        }
    }
    ~Channel()
    {
    }

private:
    int _wfd;//写描述符
    pid_t _subprocessid;//子进程id
    std::string _name;//给信道起个名字方便
};

// 形参类型和命名规范
// const &: 输出
// & : 输入输出型参数
// * : 输出型参数
//  task_t task: 回调函数(解耦常用手段) 
//（Callback Function）是指函数A作为参数传递给另一个函数B，并由函数B调用。换句话说，回调函数是一个被“调用”以完成某项操作的函数

//创建子进程和信道
void CreateChannelAndSub(int num, std::vector<Channel> *channels, task_t task)
{
    // BUG? --> fix bug
    for (int i = 0; i < num; i++)
    {
        // 1. 创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        if (n < 0)  exit(1);//创建管道失败
        // 2. 创建子进程
        pid_t id = fork();
        if (id == 0)
        {
            if (!channels->empty())//第一次为空，第二次包含第一次的写端，第三次包含前两次的写端
            {
                // 第二次之后，开始创建的管道，把包含的之前的写端关了
                for(auto &channel : *channels) channel.CloseChannel();
            }
            // child - read - 关闭fd[1]
            close(pipefd[1]);
            dup2(pipefd[0], 0); // 将管道的读端，重定向到标准输入，可以从标准输入中读取了，不用从管道读
            task();//子进程工作函数-读
            close(pipefd[0]);//结束了后全关闭然后退出,父进程会循环
            exit(0);
        }

        // 3.构建一个channel名称
        std::string channel_name = "Channel-" + std::to_string(i);
        // 父进程 - write - 关闭fd[0]
        close(pipefd[0]);
        // a. 子进程的pid b. 父进程关心的管道的w端
        channels->push_back(Channel(pipefd[1], id, channel_name));
    }
}

// 0 1 2 3 4 channelnum
int NextChannel(int channelnum)
{
    static int next = 0;
    int channel = next; 
    next++;
    next %= channelnum;
    return channel;
}

//发送任务就是写
void SendTaskCommand(Channel &channel, int taskcommand)
{
    write(channel.GetWfd(), &taskcommand, sizeof(taskcommand));
}

void ctrlProcessOnce(std::vector<Channel> &channels)
{
    sleep(1); 
    // a. 选择一个任务--得到任务码0 1 2
    int taskcommand = SelectTask();
    // b. 选择一个信道和进程 
    int channel_index = NextChannel(channels.size());
    // c. 发送任务--向指定信道发送任务码
    SendTaskCommand(channels[channel_index], taskcommand);
    std::cout << std::endl;
    std::cout << "taskcommand: " << taskcommand << " channel: "
              << channels[channel_index].GetName() << " sub process: " << channels[channel_index].GetProcessId() << std::endl;
}
//通过channel控制子进程--a.选择一个任务 b.选择一个信道和进程  c.发送一个任务
void ctrlProcess(std::vector<Channel> &channels, int times = -1)
{
    if (times > 0)
    {
        while (times--)
        {
            ctrlProcessOnce(channels);
        }
    }
    else
    {
        //没传次数就一直打印
        while (true)
        {
            ctrlProcessOnce(channels);
        }
    }
}

void CleanUpChannel(std::vector<Channel> &channels)
{
    // int num = channels.size() -1;
    // while(num >= 0)
    // { 
    //     channels[num].CloseChannel();
    //     channels[num--].Wait();
    // }//这样也可以解决关闭的问题，倒着去关闭

    for (auto &channel : channels)
    {
        channel.CloseChannel();
        channel.Wait();//如果wait写在这里不做上面判空处理会阻塞，close关闭写端没关完，由于创建很多子进程，其他子进程指向第一个管道的wfd还在，读不到数据，进程不退出，wait失败就会阻塞
    }
    // 注意
    // for (auto &channel : channels)
    // { 
    //     channel.Wait();
    // }//关完之后统一等待可以，最后一个管道只有一个写端，关闭成功，递归似的把上面的挨个关了，wait就成功了
}

// ./processpool 5
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " processnum" << std::endl;
        return 1;
    }
    int num = std::stoi(argv[1]);
    LoadTask();
    //信道数组
    std::vector<Channel> channels;
    // 1. 创建信道和子进程
    CreateChannelAndSub(num, &channels, work1);

    // 2. 通过channel控制子进程--要先构建一批任务
    ctrlProcess(channels, 5);

    // 3. 回收管道和子进程. a. 关闭所有的写端 b. 回收子进程
    CleanUpChannel(channels);

    // sleep(100);
    return 0;
}
